package com.server.config;

import com.proto_java.proto_main.ProtobufPackage;
import com.server.config.annotation.Event;
import com.server.config.annotation.WebSocketHandler;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * websocket事件注册调度器
 * 负责所有事件的注册和调度执行
 */
@Component
@Slf4j
public class WebSocketTaskScheduler implements BeanPostProcessor {

    // 事件处理回调集合。事件Id为键,具体回调方法为值(在添加的时候直接实现这里的接口)
    public static Map<Integer, BiConsumer<ProtobufPackage.PackageInfo, ChannelHandlerContext>> pool=new HashMap<>();

    /**
     * 在spring的每个bean初始化完成后调用它
     * 判断一下是否需要注册进websocket事件回调集合中
     * @param bean 对应bean
     * @param beanName bean名字
     * @return 将这个bean返回给spring容器,如果不返回那么这个bean会脱离spring的管理
     */
    @Override
    public Object postProcessAfterInitialization(@NonNull Object bean,@NonNull String beanName){

        try {
            // 判断这个bean是否为websocket的事件处理器类
            if(bean.getClass().getAnnotation(WebSocketHandler.class)!=null){
                // 根据字节码文件遍历该类中的方法
                // 其实还有第三个过滤器参数,但是这里需要过滤的步骤不多所以就省略了
                // 如果有很多层筛选的话可以直接用过滤器,它返回true就会把当前遍历的那个方法给callback了
                ReflectionUtils.doWithMethods(bean.getClass(), callback->{
                    // 获取该方法的Event注解
                    Event event = AnnotationUtils.getAnnotation(callback, Event.class);
                    // 判断Event注解是否存在,不存在则说明它不是需要的回调方法
                    if(event != null){
                        // 如果存在则添加进事件回调Map,事件回调id为键,map中函数接口的实现为值
                        pool.put(event.value(),(data,ctx)->{
                            try {
                                // 此时已经用反射拿到了对应的事件回调对象
                                // 所以直接用这个方法对象传入类对象和需要的参数调用自己即可
                                // 当其他地方通过pool拿到这个函数接口实现时,就可以直接调用它执行这里的方法了
                                // 这里在一开始注册的时候就已经给了对象,所以不会调空,只要data和ctx参数传进来没问题就一定能成功执行方法
                                callback.invoke(bean,data,ctx);
                            } catch (IllegalAccessException | InvocationTargetException e) {
                                log.error("websocket事件处理类内方法:["+callback.getName()+"]注册异常",e);
                            }
                        });
                    }
                });
            }
        } catch (BeansException e) {
            log.error("bean:["+beanName+"]初始化异常",e);
        }

        return bean;
    }
}
